package leetcode.editor.cn;
//给你一个整数 n ，请你找出并返回第 n 个 丑数 。 
//
// 丑数 就是质因子只包含 2、3 和 5 的正整数。 
//
// 
//
// 示例 1： 
//
// 
//输入：n = 10
//输出：12
//解释：[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。
// 
//
// 示例 2： 
//
// 
//输入：n = 1
//输出：1
//解释：1 通常被视为丑数。
// 
//
// 
//
// 提示： 
//
// 
// 1 <= n <= 1690 
// 
//
// Related Topics 哈希表 数学 动态规划 堆（优先队列） 👍 1153 👎 0


import java.util.PriorityQueue;
import java.util.TreeSet;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution278 {
    public int nthUglyNumber1(int n) {

        PriorityQueue<Integer> pq = new PriorityQueue<>();
        pq.offer(1);
        int res = 1;
        while (n > 0) {
            res = pq.poll();
            n--;
            if (res <= Integer.MAX_VALUE / 2 && !pq.contains(res * 2)) pq.offer(res * 2);
            if (res <= Integer.MAX_VALUE / 3 && !pq.contains(res * 3)) pq.offer(res * 3);
            if (res <= Integer.MAX_VALUE / 5 && !pq.contains(res * 5)) pq.offer(res * 5);
        }
        return res;
    }

    public int nthUglyNumber2(int n) {
        TreeSet<Integer> set = new TreeSet<>();
        set.add(1);
        int res = 1;
        while (n > 0) {
            // 拿出当前最小丑数
            res = set.pollFirst();
            n--;
            // 如果后续的丑数大于set中最大的，就没必要再加了
            if (set.size() > 0 && set.last() / 2 < res && set.size() > n) continue;
            // 以当前最小丑数逐步生成下一批次的丑数
            if ((res <= Integer.MAX_VALUE / 2)) set.add(res * 2);
            if ((res <= Integer.MAX_VALUE / 3)) set.add(res * 3);
            if ((res <= Integer.MAX_VALUE / 5)) set.add(res * 5);
        }
        return res;
    }


    /**
     *
     *
     *
     * 方法一使用最小堆，会预先存储较多的丑数，维护最小堆的过程也导致时间复杂度较高。可以使用动态规划的方法进行优化。
     *
     * 定义数组 dp，其中 dp[i] 表示第 i 个丑数，第 n 个丑数即为 dp[n]。
     *
     * 由于最小的丑数是 1，因此 dp[1]=1。
     *
     * 如何得到其余的丑数呢？定义三个指针 p2,p3,p5，表示下一个丑数是当前指针指向的丑数乘以对应的质因数。初始时，三个指针的值都是 1。
     *
     * 当 2≤i≤n 时，令 dp[i]=min(dp[p2]×2,dp[p3]×3,dp[p5]×5)，然后分别比较 dp[i] 和 dp[p2]×2,dp[p3]×3,dp[p5]×5 是否相等，如果相等则将对应的指针加 1。
     *
     *
     *
     *
     *
     * 官方题解里提到的三个指针p2，p3，p5，但是没有说明其含义，实际上pi的含义是有资格同i相乘的最小丑数的位置。
     * 这里资格指的是：如果一个丑数nums[pi]通过乘以i可以得到下一个丑数，那么这个丑数nums[pi]就永远失去了同i相乘的资格（没有必要再乘了），
     * 我们把pi++让nums[pi]指向下一个丑数即可。
     *
     * 不懂的话举例说明：
     *
     * 一开始，丑数只有{1}，1可以同2，3，5相乘，取最小的1×2=2添加到丑数序列中。
     *
     * 现在丑数中有{1，2}，在上一步中，1已经同2相乘过了，所以今后没必要再比较1×2了，我们说1失去了同2相乘的资格。
     *
     * 现在1有与3，5相乘的资格，2有与2，3，5相乘的资格，但是2×3和2×5是没必要比较的，因为有比它更小的1可以同3，5相乘，所以我们只需要比较1×3，1×5，2×2。
     *
     * 依此类推，每次我们都分别比较有资格同2，3，5相乘的最小丑数，选择最小的那个作为下一个丑数，假设选择到的这个丑数是同i（i=2，3，5）相乘得到的，
     * 所以它失去了同i相乘的资格，把对应的pi++，让pi指向下一个丑数即可。

     *
     * */
    public int nthUglyNumber(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        int p2 = 1, p3 = 1, p5 = 1;
        for (int i = 2; i <= n; i++) {
            int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
            dp[i] = Math.min(Math.min(num2, num3), num5);
            if (dp[i] == num2) {
                p2++;//当前数和2乘已经放入丑数数组了，下次没意义了，要加一
            }
            if (dp[i] == num3) {
                p3++;//当前数和3乘已经放入丑数数组了，下次没意义了，要加一
            }
            if (dp[i] == num5) {
                p5++;//当前数和5乘已经放入丑数数组了，下次没意义了，要加一
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        Solution278 s =new Solution278();
        s.nthUglyNumber(100);
    }
}
//leetcode submit region end(Prohibit modification and deletion)
